import ipaddress
import pathlib
from typing import List, Tuple


class IPFilter:
    # 可以载入 xxx.conf 文件
    def __init__(self, conf_file: str):
        self.conf_path = pathlib.Path(conf_file)
        self._load()

    def _load(self) -> None:
        # 重新加载规则，按前缀长度降序排序，保证更精确的优先匹配
        self.rules: List[Tuple[bool, ipaddress.IPv4Network | ipaddress.IPv6Network]] = []

        if not self.conf_path.exists():
            return

        with self.conf_path.open(encoding='utf-8') as f:
            for lineno, raw in enumerate(f, 1):
                line = raw.strip()
                if not line or line.startswith('#'):
                    continue
                if '#' in line:
                    line = line.split('#', 1)[0].strip()

                try:
                    if line.startswith('!'):
                        net = self._parse_net(line[1:])
                        self.rules.append((True, net))   # True = 白名单
                    else:
                        net = self._parse_net(line)
                        self.rules.append((False, net))  # False = 黑名单
                except ValueError as e:
                    print(f'[WARN] line {lineno}: {e}')

        # 按前缀长度从大到小排序，/32 > /24 > /8 …
        self.rules.sort(key=lambda t: t[1].prefixlen, reverse=True)

    @classmethod
    def from_list(cls, rules_list: List[str]) -> 'IPFilter':
        """
        从字符串列表创建 IPFilter 实例。

        :param rules_list: 包含规则的字符串列表
        :return: IPFilter 实例
        """
        # 创建实例
        instance = super().__new__(cls)
        instance.rules = []

        # 直接处理规则列表
        for lineno, line in enumerate(rules_list, 1):
            line = line.strip()
            if not line:
                continue
            try:
                if line.startswith('!'):
                    net = cls._parse_net(line[1:])
                    instance.rules.append((True, net))  # True = 白名单
                else:
                    net = cls._parse_net(line)
                    instance.rules.append((False, net))  # False = 黑名单
            except ValueError as e:
                print(f'[WARN] line {lineno}: {e}')

        # 按前缀长度从大到小排序
        instance.rules.sort(key=lambda t: t[1].prefixlen, reverse=True)
        return instance

    @staticmethod
    def _parse_net(token: str):
        token = token.strip()
        if '*' in token:
            parts = token.split('.')
            if len(parts) != 4:
                raise ValueError('通配符格式应为 a.b.c.d')
            mask = 0
            for p in parts:
                mask += (8 if p == '*' else 0)
            ip = token.replace('*', '0')
            return ipaddress.ip_network(f'{ip}/{32 - mask}', strict=False)
        else:
            return ipaddress.ip_network(token, strict=False)

    # ---------- 对外 ----------
    def reload(self) -> None:
        self._load()

    def is_blocked(self, ip_str: str) -> bool:
        """
        检查给定的 IP 地址是否被阻止

        :param ip_str: 要检查的IP地址字符串
        :return: 如果IP被阻止返回True，否则返回False

        算法逻辑：
        1. 尝试将输入字符串解析为IP地址对象
        2. 按照规则精确度从高到低的顺序匹配规则
        3. 找到第一个匹配的规则后，根据规则类型决定是否阻止
           - 白名单规则(allow=True)：放行IP(返回False)
           - 黑名单规则(allow=False)：阻止IP(返回True)
        4. 如果没有匹配任何规则，默认放行(返回False)
        """
        try:
            ip_obj = ipaddress.ip_address(ip_str)
        except ValueError:
            return False

        # 按“最精确优先”的顺序依次匹配
        for allow, net in self.rules:
            if ip_obj in net:
                return not allow  # allow=True → 放行(False)；allow=False → 拦截(True)

        return False  # 默认放行
